package com.flow.service.provider.experiment.apply.service.impl;

import com.flow.common.facade.experiment.apply.immutable.ApplyStatusImmutable;
import com.flow.common.facade.experiment.apply.immutable.ApplyTypeImmutable;
import com.flow.framework.base.service.alarm.IAlarmService;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.json.JsonObject;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.pojo.vo.PageVo;
import com.flow.framework.core.system.thread.pool.executor.ThreadPoolExecutor;
import com.flow.framework.core.system.thread.pool.policy.RejectedPolicy;
import com.flow.framework.core.system.thread.pool.task.BaseRunnable;
import com.flow.framework.facade.alarm.immutable.AlarmSeverityImmutable;
import com.flow.framework.facade.alarm.immutable.AlarmTypeImmutable;
import com.flow.framework.facade.alarm.pojo.dto.AlarmReportModuleDto;
import com.flow.framework.lock.helper.LockHelper;
import com.flow.framework.mq.pojo.bo.QueueBo;
import com.flow.framework.mq.producer.MessageQueueClient;
import com.flow.framework.persistence.helper.TransactionHelper;
import com.flow.service.facade.check.user.pojo.vo.UserModuleVo;
import com.flow.service.facade.check.user.service.IUserModuleCacheService;
import com.flow.service.facade.experiment.ExperimentErrorCode;
import com.flow.service.facade.experiment.ExperimentModuleConstant;
import com.flow.service.facade.experiment.apply.pojo.dto.ApplyModuleDto;
import com.flow.service.facade.experiment.apply.pojo.dto.notify.ApplyApprovalNotifyDto;
import com.flow.service.facade.experiment.apply.pojo.vo.ApplyModuleVo;
import com.flow.service.provider.experiment.apply.convert.ApplyConverter;
import com.flow.service.provider.experiment.apply.persistence.service.IApplyPoService;
import com.flow.service.provider.experiment.apply.pojo.dto.ApplyApprovalDto;
import com.flow.service.provider.experiment.apply.pojo.dto.ApplyPageQueryDto;
import com.flow.service.provider.experiment.apply.pojo.po.Apply;
import com.flow.service.provider.experiment.apply.pojo.vo.ApplyVo;
import com.flow.service.provider.experiment.apply.service.IApplyService;
import com.flow.service.provider.experiment.constant.ExperimentAlarmConstant;
import com.flow.service.provider.experiment.enumeration.ApplyLockKeyEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 申请业务服务实现
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2023/4/30
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ApplyServiceImpl implements IApplyService {

    private static final Map<String, QueueBo> APPLY_TYPE_AND_QUEUE_MAP = new HashMap<String, QueueBo>(16) {{
        QueueBo queueBo = QueueBo.build("toleration:experiment:apply",
                "toleration:experiment:apply_approval_notify");
        put(ApplyTypeImmutable.COMPUTER_USE_APPLY, queueBo);
        put(ApplyTypeImmutable.COMPUTER_BACK_APPLY, queueBo);
        put(ApplyTypeImmutable.COMPUTER_RECYCLE_SALE_APPLY, queueBo);
    }};

    private static final ThreadPoolExecutor APPLY_NOTIFY_THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(4, 8, 30 * 60 * 100, 1024,
            "apply_approval_notify", RejectedPolicy.THROW_EXCEPTION_POLICY);

    private static final long APPLY_APPROVAL_NOTIFY_TASK_TIMEOUT = 30 * 60 * 100;

    private final IApplyPoService applyPoService;

    private final IUserModuleCacheService userModuleCacheService;

    private final MessageQueueClient messageQueueClient;

    private final IAlarmService alarmService;

    /**
     * 创建申请
     *
     * @param dto dto
     * @return
     */
    @Override
    public ApplyModuleVo create(ApplyModuleDto dto) {
        String userId = dto.getUserId();
        UserModuleVo userModuleVo = userModuleCacheService.getById(userId, false);
        String applyNo = dto.getApplyNo();
        Apply apply = ApplyConverter.convertToPo(dto);
        apply.setUserName(userModuleVo.getUserName());
        apply.setApplyStatus(ApplyStatusImmutable.PENDING);
        LockHelper.voidLockExecute(null, ApplyLockKeyEnum.APPLY_NO_CHECK_LOCK_KEY,
                Collections.singletonList(applyNo), () -> {
                    boolean applyNoExist = applyPoService.isApplyNoExist(applyNo);
                    if (applyNoExist) {
                        log.error("apply no exist, no : {}", applyNo);
                        throw new CheckedException(ExperimentErrorCode.APPLY_NO_EXIST_ERROR);
                    }
                    TransactionHelper.localNewTransactionVoidExec(() -> applyPoService.save(apply));
                });
        Apply applyInDb = applyPoService.getById(apply.getId(), false);
        return ApplyConverter.convertToModuleVo(applyInDb);
    }

    /**
     * 根据申请编号获取申请
     *
     * @param applyNo   applyNo
     * @param allowNull allowNull
     * @return
     */
    @Override
    public ApplyModuleVo getByApplyNo(String applyNo, boolean allowNull) {
        Apply apply = applyPoService.getByApplyNo(applyNo, allowNull);
        return ApplyConverter.convertToModuleVo(apply);
    }

    /**
     * 申请分页查询
     *
     * @param dto dto
     * @return
     */
    @Override
    public PageVo<ApplyVo> page(ApplyPageQueryDto dto) {
        return applyPoService.page(dto);
    }

    /**
     * 申请审批
     *
     * @param dto dto
     * @return
     */
    @Override
    public ApplyVo applyApproval(ApplyApprovalDto dto) {
        String id = dto.getId();
        Apply apply = applyPoService.getById(id, false);
        String applyStatus = apply.getApplyStatus();
        if (!ApplyStatusImmutable.PENDING.equals(applyStatus)) {
            log.error("apply status expect PENDING, current : {}", applyStatus);
            throw new CheckedException(ExperimentErrorCode.APPLY_STATUS_ERROR);
        }
        String applyType = apply.getApplyType();
        QueueBo queueBo = APPLY_TYPE_AND_QUEUE_MAP.get(applyType);
        VerifyUtil.requireNotNull(queueBo, () -> {
            log.error("can't find apply approval notify config. apply type : {}", applyType);
            throw new CheckedException(ExperimentErrorCode.APPLY_APPROVAL_NOTIFY_CONFIG_ERROR);
        });

        LockHelper.voidLockExecute(null, ApplyLockKeyEnum.APPLY_APPROVAL_CHECK_LOCK_KEY,
                Collections.singletonList(id), () -> {
                    Apply applyDcl = applyPoService.getById(id, false);
                    String applyStatusDcl = applyDcl.getApplyStatus();
                    if (!ApplyStatusImmutable.PENDING.equals(applyStatusDcl)) {
                        log.error("apply status expect PENDING, current : {}", applyStatusDcl);
                        throw new CheckedException(ExperimentErrorCode.APPLY_STATUS_ERROR);
                    }
                    Apply updateApply = new Apply();
                    updateApply.setId(id);
                    if (VerifyUtil.isTrue(dto.getIsPass())) {
                        updateApply.setApplyStatus(ApplyStatusImmutable.PASS);
                    } else {
                        updateApply.setApplyStatus(ApplyStatusImmutable.REFUSE);
                    }
                    TransactionHelper.localNewTransactionVoidExec(() -> applyPoService.updateById(updateApply));
                });
        Apply applyInDb = applyPoService.getById(id, false);
        applyApprovalNotify(applyInDb, RejectedPolicy.DISCARD_POLICY);
        return ApplyConverter.convertToVo(applyInDb);
    }

    /**
     * 申请审批消息通知
     *
     * @param apply          apply
     * @param rejectedPolicy rejectedPolicy
     */
    @Override
    public void applyApprovalNotify(Apply apply, RejectedPolicy rejectedPolicy) {
        APPLY_NOTIFY_THREAD_POOL_EXECUTOR.submit(new BaseRunnable(APPLY_APPROVAL_NOTIFY_TASK_TIMEOUT) {
            @Override
            protected void execute() {
                try {
                    if (VerifyUtil.isTrue(apply.getIsNotified())) {
                        return;
                    }
                    String applyStatus = apply.getApplyStatus();
                    if (!ApplyStatusImmutable.PASS.equals(applyStatus) && !ApplyStatusImmutable.REFUSE.equals(applyStatus)) {
                        return;
                    }
                    String applyType = apply.getApplyType();
                    QueueBo queueBo = APPLY_TYPE_AND_QUEUE_MAP.get(applyType);
                    VerifyUtil.requireNotNull(queueBo, () -> {
                        log.error("can't find apply approval notify config. apply type : {}", applyType);
                        throw new CheckedException(ExperimentErrorCode.APPLY_APPROVAL_NOTIFY_CONFIG_ERROR);
                    });
                    String id = apply.getId();
                    LockHelper.voidLockExecute(null, ApplyLockKeyEnum.APPLY_APPROVAL_NOTIFY_LOCK_KEY,
                            Collections.singletonList(id), () -> {
                                Apply applyInDb = applyPoService.getById(id, false);
                                if (VerifyUtil.isTrue(applyInDb.getIsNotified())) {
                                    return;
                                }
                                messageQueueClient.sendAndConfirm(queueBo, new ApplyApprovalNotifyDto(applyInDb.getApplyNo(),
                                        applyType, ApplyStatusImmutable.PASS.equals(applyInDb.getApplyStatus()),
                                        applyInDb.getResourceId()));
                                Apply updateApply = new Apply();
                                updateApply.setId(id);
                                updateApply.setIsNotified(true);
                                TransactionHelper.localNewTransactionVoidExec(() -> applyPoService.updateById(updateApply));
                            });
                } catch (Exception e) {
                    log.error("apply result notify error. apply no : {}", apply.getApplyNo(), e);

                    // 上报告警
                    AlarmReportModuleDto alarmReportModuleDto = new AlarmReportModuleDto();
                    alarmReportModuleDto.setModuleName(ExperimentModuleConstant.MODULE_NAME);
                    alarmReportModuleDto.setSourceType(ExperimentAlarmConstant.APPLY_RESULT_NOTIFY_FAILED);
                    alarmReportModuleDto.setSourceId(apply.getId());
                    alarmReportModuleDto.setAlarmType(AlarmTypeImmutable.BUSINESS);
                    alarmReportModuleDto.setAlarmSeverity(AlarmSeverityImmutable.MAJOR);
                    alarmReportModuleDto.setAlarmTime(LocalDateTime.now());
                    alarmReportModuleDto.setAlarmVersion(1);
                    alarmReportModuleDto.setAlarmParams(JsonObject.toString(Collections.singletonList(apply)));
                    alarmService.reportAlarm(alarmReportModuleDto);
                    throw e;
                }
            }
        }, rejectedPolicy);
    }
}